<!DOCTYPE html>


<html lang="zh-CN">


<head>
  <meta charset="utf-8" />
    
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
  <title>
    搞定python多线程和多进程.md |  
  </title>
  <meta name="generator" content="hexo-theme-ayer">
  
  <link rel="shortcut icon" href="/favicon.ico" />
  
  
<link rel="stylesheet" href="/dist/main.css">

  
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Shen-Yu/cdn/css/remixicon.min.css">

  
<link rel="stylesheet" href="/css/custom.css">

  
  
<script src="https://cdn.jsdelivr.net/npm/pace-js@1.0.2/pace.min.js"></script>

  
  

  

</head>

</html>

<body>
  <div id="app">
    
      
    <main class="content on">
      <section class="outer">
  <article
  id="post-develop/搞定python多线程和多进程"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h1 class="article-title sea-center" style="border-left:0" itemprop="name">
  搞定python多线程和多进程.md
</h1>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/11/11/develop/%E6%90%9E%E5%AE%9Apython%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%92%8C%E5%A4%9A%E8%BF%9B%E7%A8%8B/" class="article-date">
  <time datetime="2020-11-10T16:00:00.000Z" itemprop="datePublished">2020-11-11</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/develop/">develop</a>
  </div>
  
<div class="word_count">
    <span class="post-time">
        <span class="post-meta-item-icon">
            <i class="ri-quill-pen-line"></i>
            <span class="post-meta-item-text"> 字数统计:</span>
            <span class="post-count">5.7k</span>
        </span>
    </span>

    <span class="post-time">
        &nbsp; | &nbsp;
        <span class="post-meta-item-icon">
            <i class="ri-book-open-line"></i>
            <span class="post-meta-item-text"> 阅读时长≈</span>
            <span class="post-count">24 分钟</span>
        </span>
    </span>
</div>
 
    </div>
      
    <div class="tocbot"></div>




  
    <div class="article-entry" itemprop="articleBody">
       
  <h1 id="搞定python多线程和多进程"><a href="#搞定python多线程和多进程" class="headerlink" title="搞定python多线程和多进程"></a>搞定python多线程和多进程</h1><blockquote>
<p>来源:<a target="_blank" rel="noopener" href="https://www.cnblogs.com/whatisfantasy/p/6440585.html">https://www.cnblogs.com/whatisfantasy/p/6440585.html</a></p>
</blockquote>
<p>###1 概念梳理：</p>
<h4 id="1-1-线程"><a href="#1-1-线程" class="headerlink" title="1.1 线程"></a>1.1 线程</h4><h5 id="1-1-1-什么是线程"><a href="#1-1-1-什么是线程" class="headerlink" title="1.1.1 什么是线程"></a>1.1.1 什么是线程</h5><p><strong>线程</strong>是操作系统能够进行运算调度的最小单位。它被包含在进程之中，是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流，一个进程中可以并发多个线程，每条线程并行执行不同的任务。一个线程是一个execution context（执行上下文），即一个cpu执行时所需要的一串指令。</p>
<h5 id="1-1-2-线程的工作方式"><a href="#1-1-2-线程的工作方式" class="headerlink" title="1.1.2 线程的工作方式"></a>1.1.2 线程的工作方式</h5><p>假设你正在读一本书，没有读完，你想休息一下，但是你想在回来时恢复到当时读的具体进度。有一个方法就是记下页数、行数与字数这三个数值，这些数值就是execution context。如果你的室友在你休息的时候，使用相同的方法读这本书。你和她只需要这三个数字记下来就可以在交替的时间共同阅读这本书了。</p>
<p>线程的工作方式与此类似。CPU会给你一个在同一时间能够做多个运算的幻觉，实际上它在每个运算上只花了极少的时间，本质上CPU同一时刻只干了一件事。它能这样做就是因为它有每个运算的execution context。就像你能够和你朋友共享同一本书一样，多任务也能共享同一块CPU。</p>
<h4 id="1-2-进程"><a href="#1-2-进程" class="headerlink" title="1.2 进程"></a>1.2 进程</h4><p>一个程序的执行实例就是一个<strong>进程</strong>。每一个进程提供执行程序所需的所有资源。（进程本质上是资源的集合）</p>
<p>一个进程有一个虚拟的地址空间、可执行的代码、操作系统的接口、安全的上下文（记录启动该进程的用户和权限等等）、唯一的进程ID、环境变量、优先级类、最小和最大的工作空间（内存空间），还要有至少一个线程。</p>
<p>每一个进程启动时都会最先产生一个线程，即主线程。然后主线程会再创建其他的子线程。</p>
<blockquote>
<p>与进程相关的资源包括:</p>
</blockquote>
<ul>
<li>内存页（<strong>同一个进程中的所有线程共享同一个内存空间</strong>）</li>
<li>文件描述符(e.g. open sockets)</li>
<li>安全凭证（e.g.启动该进程的用户ID）</li>
</ul>
<h4 id="1-3-进程与线程区别"><a href="#1-3-进程与线程区别" class="headerlink" title="1.3 进程与线程区别"></a>1.3 进程与线程区别</h4><p>1.同一个进程中的线程共享同一内存空间，但是进程之间是独立的。<br>2.同一个进程中的所有线程的数据是共享的（进程通讯），进程之间的数据是独立的。<br>3.对主线程的修改可能会影响其他线程的行为，但是父进程的修改（除了删除以外）不会影响其他子进程。<br>4.线程是一个上下文的执行指令，而进程则是与运算相关的一簇资源。<br>5.同一个进程的线程之间可以直接通信，但是进程之间的交流需要借助中间代理来实现。<br>6.创建新的线程很容易，但是创建新的进程需要对父进程做一次复制。<br>7.一个线程可以操作同一进程的其他线程，但是进程只能操作其子进程。<br>8.线程启动速度快，进程启动速度慢（但是两者运行速度没有可比性）。</p>
<h3 id="2-多线程"><a href="#2-多线程" class="headerlink" title="2 多线程"></a>2 多线程</h3><h4 id="2-1-线程常用方法"><a href="#2-1-线程常用方法" class="headerlink" title="2.1 线程常用方法"></a>2.1 线程常用方法</h4><table>
<thead>
<tr>
<th>方法</th>
<th>注释</th>
</tr>
</thead>
<tbody><tr>
<td>start()</td>
<td>线程准备就绪，等待CPU调度</td>
</tr>
<tr>
<td>setName()</td>
<td>为线程设置名称</td>
</tr>
<tr>
<td>getName()</td>
<td>获取线程名称</td>
</tr>
<tr>
<td>setDaemon(True)</td>
<td>设置为守护线程</td>
</tr>
<tr>
<td>join()</td>
<td>逐个执行每个线程，执行完毕后继续往下执行</td>
</tr>
<tr>
<td>run()</td>
<td>线程被cpu调度后自动执行线程对象的run方法，如果想自定义线程类，直接重写run方法就行了</td>
</tr>
</tbody></table>
<h5 id="2-1-1-Thread类"><a href="#2-1-1-Thread类" class="headerlink" title="2.1.1 Thread类"></a>2.1.1 Thread类</h5><p>1.普通创建方式</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">import threading</span><br><span class="line">import time</span><br><span class="line"></span><br><span class="line">def run(n):</span><br><span class="line">    print(&quot;task&quot;, n)</span><br><span class="line">    time.sleep(1)</span><br><span class="line">    print(&#39;2s&#39;)</span><br><span class="line">    time.sleep(1)</span><br><span class="line">    print(&#39;1s&#39;)</span><br><span class="line">    time.sleep(1)</span><br><span class="line">    print(&#39;0s&#39;)</span><br><span class="line">    time.sleep(1)</span><br><span class="line"></span><br><span class="line">t1 &#x3D; threading.Thread(target&#x3D;run, args&#x3D;(&quot;t1&quot;,))</span><br><span class="line">t2 &#x3D; threading.Thread(target&#x3D;run, args&#x3D;(&quot;t2&quot;,))</span><br><span class="line">t1.start()</span><br><span class="line">t2.start()</span><br><span class="line"></span><br><span class="line">&quot;&quot;&quot;</span><br><span class="line">task t1</span><br><span class="line">task t2</span><br><span class="line">2s</span><br><span class="line">2s</span><br><span class="line">1s</span><br><span class="line">1s</span><br><span class="line">0s</span><br><span class="line">0s</span><br><span class="line">&quot;&quot;&quot;</span><br></pre></td></tr></table></figure>

<p>2.继承threading.Thread来自定义线程类<br>其本质是重构Thread类中的run方法</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">import threading</span><br><span class="line">import time</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">class MyThread(threading.Thread):</span><br><span class="line">    def __init__(self, n):</span><br><span class="line">        super(MyThread, self).__init__()  # 重构run函数必须要写</span><br><span class="line">        self.n &#x3D; n</span><br><span class="line"></span><br><span class="line">    def run(self):</span><br><span class="line">        print(&quot;task&quot;, self.n)</span><br><span class="line">        time.sleep(1)</span><br><span class="line">        print(&#39;2s&#39;)</span><br><span class="line">        time.sleep(1)</span><br><span class="line">        print(&#39;1s&#39;)</span><br><span class="line">        time.sleep(1)</span><br><span class="line">        print(&#39;0s&#39;)</span><br><span class="line">        time.sleep(1)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">if __name__ &#x3D;&#x3D; &quot;__main__&quot;:</span><br><span class="line">    t1 &#x3D; MyThread(&quot;t1&quot;)</span><br><span class="line">    t2 &#x3D; MyThread(&quot;t2&quot;)</span><br><span class="line"></span><br><span class="line">    t1.start()</span><br><span class="line">    t2.start()</span><br></pre></td></tr></table></figure>

<h5 id="2-1-2-计算子线程执行的时间"><a href="#2-1-2-计算子线程执行的时间" class="headerlink" title="2.1.2 计算子线程执行的时间"></a>2.1.2 计算子线程执行的时间</h5><p>注：sleep的时候是不会占用cpu的,在sleep的时候操作系统会把线程暂时挂起。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line">join()  #等此线程执行完后，再执行其他线程或主线程</span><br><span class="line">threading.current_thread()      #输出当前线程</span><br><span class="line">import threading</span><br><span class="line">import time</span><br><span class="line"></span><br><span class="line">def run(n):</span><br><span class="line">    print(&quot;task&quot;, n,threading.current_thread())    #输出当前的线程</span><br><span class="line">    time.sleep(1)</span><br><span class="line">    print(&#39;3s&#39;)</span><br><span class="line">    time.sleep(1)</span><br><span class="line">    print(&#39;2s&#39;)</span><br><span class="line">    time.sleep(1)</span><br><span class="line">    print(&#39;1s&#39;)</span><br><span class="line"></span><br><span class="line">strat_time &#x3D; time.time()</span><br><span class="line"></span><br><span class="line">t_obj &#x3D; []   #定义列表用于存放子线程实例</span><br><span class="line"></span><br><span class="line">for i in range(3):</span><br><span class="line">    t &#x3D; threading.Thread(target&#x3D;run, args&#x3D;(&quot;t-%s&quot; % i,))</span><br><span class="line">    t.start()</span><br><span class="line">    t_obj.append(t)</span><br><span class="line">    </span><br><span class="line">&quot;&quot;&quot;</span><br><span class="line">由主线程生成的三个子线程</span><br><span class="line">task t-0 &lt;Thread(Thread-1, started 44828)&gt;</span><br><span class="line">task t-1 &lt;Thread(Thread-2, started 42804)&gt;</span><br><span class="line">task t-2 &lt;Thread(Thread-3, started 41384)&gt;</span><br><span class="line">&quot;&quot;&quot;</span><br><span class="line"></span><br><span class="line">for tmp in t_obj:</span><br><span class="line">    t.join()            #为每个子线程添加join之后，主线程就会等这些子线程执行完之后再执行。</span><br><span class="line"></span><br><span class="line">print(&quot;cost:&quot;, time.time() - strat_time) #主线程</span><br><span class="line"></span><br><span class="line">print(threading.current_thread())       #输出当前线程</span><br><span class="line">&quot;&quot;&quot;</span><br><span class="line">&lt;_MainThread(MainThread, started 43740)&gt;</span><br><span class="line">&quot;&quot;&quot;</span><br></pre></td></tr></table></figure>

<h4 id="2-1-3-统计当前活跃的线程数"><a href="#2-1-3-统计当前活跃的线程数" class="headerlink" title="2.1.3 统计当前活跃的线程数"></a>2.1.3 统计当前活跃的线程数</h4><p>由于主线程比子线程快很多，当主线程执行active_count()时，其他子线程都还没执行完毕，因此利用主线程统计的活跃的线程数num = sub_num(子线程数量)+1(主线程本身)</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">import threading</span><br><span class="line">import time</span><br><span class="line"></span><br><span class="line">def run(n):</span><br><span class="line">    print(&quot;task&quot;, n)    </span><br><span class="line">    time.sleep(1)       #此时子线程停1s</span><br><span class="line"></span><br><span class="line">for i in range(3):</span><br><span class="line">    t &#x3D; threading.Thread(target&#x3D;run, args&#x3D;(&quot;t-%s&quot; % i,))</span><br><span class="line">    t.start()</span><br><span class="line"></span><br><span class="line">time.sleep(0.5)     #主线程停0.5秒</span><br><span class="line">print(threading.active_count()) #输出当前活跃的线程数</span><br><span class="line"></span><br><span class="line">&quot;&quot;&quot;</span><br><span class="line">task t-0</span><br><span class="line">task t-1</span><br><span class="line">task t-2</span><br><span class="line">4</span><br><span class="line">&quot;&quot;&quot;</span><br></pre></td></tr></table></figure>

<p>由于主线程比子线程慢很多，当主线程执行active_count()时，其他子线程都已经执行完毕，因此利用主线程统计的活跃的线程数num = 1(主线程本身)</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">import threading</span><br><span class="line">import time</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">def run(n):</span><br><span class="line">    print(&quot;task&quot;, n)</span><br><span class="line">    time.sleep(0.5)       #此时子线程停0.5s</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">for i in range(3):</span><br><span class="line">    t &#x3D; threading.Thread(target&#x3D;run, args&#x3D;(&quot;t-%s&quot; % i,))</span><br><span class="line">    t.start()</span><br><span class="line"></span><br><span class="line">time.sleep(1)     #主线程停1秒</span><br><span class="line">print(threading.active_count()) #输出活跃的线程数</span><br><span class="line">&quot;&quot;&quot;</span><br><span class="line">task t-0</span><br><span class="line">task t-1</span><br><span class="line">task t-2</span><br><span class="line">1</span><br><span class="line">&quot;&quot;&quot;</span><br></pre></td></tr></table></figure>

<p>此外我们还能发现在python内部默认会等待最后一个进程执行完后再执行exit()，或者说python内部在此时有一个隐藏的join()。</p>
<h4 id="2-2-守护进程"><a href="#2-2-守护进程" class="headerlink" title="2.2 守护进程"></a>2.2 守护进程</h4><p>我们看下面这个例子，这里使用setDaemon(True)把所有的子线程都变成了主线程的守护线程，因此当主进程结束后，子线程也会随之结束。所以当主线程结束后，整个程序就退出了。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">import threading</span><br><span class="line">import time</span><br><span class="line"></span><br><span class="line">def run(n):</span><br><span class="line">    print(&quot;task&quot;, n)</span><br><span class="line">    time.sleep(1)       #此时子线程停1s</span><br><span class="line">    print(&#39;3&#39;)</span><br><span class="line">    time.sleep(1)</span><br><span class="line">    print(&#39;2&#39;)</span><br><span class="line">    time.sleep(1)</span><br><span class="line">    print(&#39;1&#39;)</span><br><span class="line"></span><br><span class="line">for i in range(3):</span><br><span class="line">    t &#x3D; threading.Thread(target&#x3D;run, args&#x3D;(&quot;t-%s&quot; % i,))</span><br><span class="line">    t.setDaemon(True)   #把子进程设置为守护线程，必须在start()之前设置</span><br><span class="line">    t.start()</span><br><span class="line"></span><br><span class="line">time.sleep(0.5)     #主线程停0.5秒</span><br><span class="line">print(threading.active_count()) #输出活跃的线程数</span><br><span class="line">&quot;&quot;&quot;</span><br><span class="line">task t-0</span><br><span class="line">task t-1</span><br><span class="line">task t-2</span><br><span class="line">4</span><br><span class="line"></span><br><span class="line">Process finished with exit code 0</span><br><span class="line">&quot;&quot;&quot;</span><br></pre></td></tr></table></figure>

<h4 id="2-3-GIL"><a href="#2-3-GIL" class="headerlink" title="2.3 GIL"></a>2.3 GIL</h4><p>在非python环境中，单核情况下，同时只能有一个任务执行。多核时可以支持多个线程同时执行。但是在python中，无论有多少核，同时只能执行一个线程。究其原因，这就是由于GIL的存在导致的。</p>
<p>GIL的全称是Global Interpreter Lock(全局解释器锁)，来源是python设计之初的考虑，为了数据安全所做的决定。某个线程想要执行，必须先拿到GIL，我们可以把GIL看作是“通行证”，并且在一个python进程中，GIL只有一个。拿不到通行证的线程，就不允许进入CPU执行。GIL只在cpython中才有，因为cpython调用的是c语言的原生线程，所以他不能直接操作cpu，只能利用GIL保证同一时间只能有一个线程拿到数据。而在pypy和jpython中是没有GIL的。</p>
<p><strong>Python多线程的工作过程：</strong><br>python在使用多线程的时候，调用的是c语言的原生线程。</p>
<ol>
<li>拿到公共数据</li>
<li>申请gil</li>
<li>python解释器调用os原生线程</li>
<li>os操作cpu执行运算</li>
<li>当该线程执行时间到后，无论运算是否已经执行完，gil都被要求释放</li>
<li>进而由其他进程重复上面的过程</li>
<li>等其他进程执行完后，又会切换到之前的线程（从他记录的上下文继续执行）<br>整个过程是每个线程执行自己的运算，当执行时间到就进行切换（context switch）。</li>
</ol>
<ul>
<li>python针对不同类型的代码执行效率也是不同的：</li>
</ul>
<blockquote>
<p>1、CPU密集型代码(各种循环处理、计算等等)，在这种情况下，由于计算工作多，ticks计数很快就会达到阈值，然后触发GIL的释放与再竞争（多个线程来回切换当然是需要消耗资源的），所以python下的多线程对CPU密集型代码并不友好。<br>2、IO密集型代码(文件处理、网络爬虫等涉及文件读写的操作)，多线程能够有效提升效率(单线程下有IO操作会进行IO等待，造成不必要的时间浪费，而开启多线程能在线程A等待时，自动切换到线程B，可以不浪费CPU的资源，从而能提升程序执行效率)。所以python的多线程对IO密集型代码比较友好。</p>
</blockquote>
<ul>
<li>使用建议？</li>
</ul>
<blockquote>
<p>python下想要充分利用多核CPU，就用多进程。因为每个进程有各自独立的GIL，互不干扰，这样就可以真正意义上的并行执行，在python中，多进程的执行效率优于多线程(仅仅针对多核CPU而言)。</p>
</blockquote>
<ul>
<li>GIL在python中的版本差异：</li>
</ul>
<blockquote>
<p>1、在python2.x里，GIL的释放逻辑是当前线程遇见<code>IO操作</code>或者<code>ticks计数达到100</code>时进行释放。（ticks可以看作是python自身的一个计数器，专门做用于GIL，每次释放后归零，这个计数可以通过sys.setcheckinterval 来调整）。而每次释放GIL锁，线程进行锁竞争、切换线程，会消耗资源。并且由于GIL锁存在，python里一个进程永远只能同时执行一个线程(拿到GIL的线程才能执行)，这就是为什么在多核CPU上，python的多线程效率并不高。<br>2、在python3.x中，GIL不使用ticks计数，改为使用计时器（执行时间达到阈值后，当前线程释放GIL），这样对CPU密集型程序更加友好，但依然没有解决GIL导致的同一时间只能执行一个线程的问题，所以效率依然不尽如人意。</p>
</blockquote>
<h4 id="2-4-线程锁"><a href="#2-4-线程锁" class="headerlink" title="2.4 线程锁"></a>2.4 线程锁</h4><p>由于线程之间是进行随机调度，并且每个线程可能只执行n条执行之后，当多个线程同时修改同一条数据时可能会出现脏数据，所以，出现了线程锁，即同一时刻允许一个线程执行操作。线程锁用于锁定资源，你可以定义多个锁, 像下面的代码, 当你需要独占某一资源时，任何一个锁都可以锁这个资源，就好比你用不同的锁都可以把相同的一个门锁住是一个道理。</p>
<p>由于线程之间是进行随机调度，如果有多个线程同时操作一个对象，如果没有很好地保护该对象，会造成程序结果的不可预期，我们也称此为“线程不安全”。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">#实测：在python2.7、mac os下，运行以下代码可能会产生脏数据。但是在python3中就不一定会出现下面的问题。</span><br><span class="line"></span><br><span class="line">import threading</span><br><span class="line">import time</span><br><span class="line"></span><br><span class="line">def run(n):</span><br><span class="line">    global num</span><br><span class="line">    num +&#x3D; 1</span><br><span class="line"></span><br><span class="line">num &#x3D; 0</span><br><span class="line">t_obj &#x3D; [] </span><br><span class="line"></span><br><span class="line">for i in range(20000):</span><br><span class="line">    t &#x3D; threading.Thread(target&#x3D;run, args&#x3D;(&quot;t-%s&quot; % i,))</span><br><span class="line">    t.start()</span><br><span class="line">    t_obj.append(t)</span><br><span class="line"></span><br><span class="line">for t in t_obj:</span><br><span class="line">    t.join()</span><br><span class="line"></span><br><span class="line">print &quot;num:&quot;, num</span><br><span class="line">&quot;&quot;&quot;</span><br><span class="line">产生脏数据后的运行结果：</span><br><span class="line">num: 19999</span><br><span class="line">&quot;&quot;&quot;</span><br></pre></td></tr></table></figure>

<h4 id="2-5-互斥锁（mutex）"><a href="#2-5-互斥锁（mutex）" class="headerlink" title="2.5 互斥锁（mutex）"></a>2.5 互斥锁（mutex）</h4><p>为了方式上面情况的发生，就出现了互斥锁(Lock)</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">import threading</span><br><span class="line">import time</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">def run(n):</span><br><span class="line">    lock.acquire()  #获取锁</span><br><span class="line">    global num</span><br><span class="line">    num +&#x3D; 1</span><br><span class="line">    lock.release()  #释放锁</span><br><span class="line"></span><br><span class="line">lock &#x3D; threading.Lock()     #实例化一个锁对象</span><br><span class="line"></span><br><span class="line">num &#x3D; 0</span><br><span class="line">t_obj &#x3D; []  </span><br><span class="line"></span><br><span class="line">for i in range(20000):</span><br><span class="line">    t &#x3D; threading.Thread(target&#x3D;run, args&#x3D;(&quot;t-%s&quot; % i,))</span><br><span class="line">    t.start()</span><br><span class="line">    t_obj.append(t)</span><br><span class="line"></span><br><span class="line">for t in t_obj:</span><br><span class="line">    t.join()</span><br><span class="line"></span><br><span class="line">print &quot;num:&quot;, num</span><br></pre></td></tr></table></figure>

<h4 id="2-6-递归锁"><a href="#2-6-递归锁" class="headerlink" title="2.6 递归锁"></a>2.6 递归锁</h4><p>RLcok类的用法和Lock类一模一样，但它支持嵌套，，在多个锁没有释放的时候一般会使用使用RLcok类。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">import threading</span><br><span class="line">import time</span><br><span class="line">   </span><br><span class="line">gl_num &#x3D; 0</span><br><span class="line">   </span><br><span class="line">lock &#x3D; threading.RLock()</span><br><span class="line">   </span><br><span class="line">def Func():</span><br><span class="line">    lock.acquire()</span><br><span class="line">    global gl_num</span><br><span class="line">    gl_num +&#x3D;1</span><br><span class="line">    time.sleep(1)</span><br><span class="line">    print gl_num</span><br><span class="line">    lock.release()</span><br><span class="line">       </span><br><span class="line">for i in range(10):</span><br><span class="line">    t &#x3D; threading.Thread(target&#x3D;Func)</span><br><span class="line">    t.start()</span><br></pre></td></tr></table></figure>

<h4 id="2-7-信号量（BoundedSemaphore类）"><a href="#2-7-信号量（BoundedSemaphore类）" class="headerlink" title="2.7 信号量（BoundedSemaphore类）"></a>2.7 信号量（BoundedSemaphore类）</h4><p>互斥锁同时只允许一个线程更改数据，而Semaphore是同时允许一定数量的线程更改数据 ，比如厕所有3个坑，那最多只允许3个人上厕所，后面的人只能等里面有人出来了才能再进去。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">import threading</span><br><span class="line">import time</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">def run(n):</span><br><span class="line">    semaphore.acquire()   #加锁</span><br><span class="line">    time.sleep(1)</span><br><span class="line">    print(&quot;run the thread:%s\n&quot; % n)</span><br><span class="line">    semaphore.release()     #释放</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">num &#x3D; 0</span><br><span class="line">semaphore &#x3D; threading.BoundedSemaphore(5)  # 最多允许5个线程同时运行</span><br><span class="line"></span><br><span class="line">for i in range(22):</span><br><span class="line">    t &#x3D; threading.Thread(target&#x3D;run, args&#x3D;(&quot;t-%s&quot; % i,))</span><br><span class="line">    t.start()</span><br><span class="line"></span><br><span class="line">while threading.active_count() !&#x3D; 1:</span><br><span class="line">    pass  # print threading.active_count()</span><br><span class="line">else:</span><br><span class="line">    print(&#39;-----all threads done-----&#39;)</span><br></pre></td></tr></table></figure>

<h3 id="2-8-事件（Event类）"><a href="#2-8-事件（Event类）" class="headerlink" title="2.8 事件（Event类）"></a>2.8 事件（Event类）</h3><p>python线程的<strong>事件</strong>用于主线程控制其他线程的执行，事件是一个简单的线程<strong>同步</strong>对象，其主要提供以下几个方法：</p>
<table>
<thead>
<tr>
<th>方法</th>
<th>注释</th>
</tr>
</thead>
<tbody><tr>
<td>clear</td>
<td>将flag设置为“False”</td>
</tr>
<tr>
<td>set</td>
<td>将flag设置为“True”</td>
</tr>
<tr>
<td>is_set</td>
<td>判断是否设置了flag</td>
</tr>
<tr>
<td>wait</td>
<td>会一直监听flag，如果没有检测到flag就一直处于阻塞状态</td>
</tr>
</tbody></table>
<p>事件处理的机制：全局定义了一个“Flag”，当flag值为“False”，那么event.wait()就会阻塞，当flag值为“True”，那么event.wait()便不再阻塞。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line">#利用Event类模拟红绿灯</span><br><span class="line">import threading</span><br><span class="line">import time</span><br><span class="line"></span><br><span class="line">event &#x3D; threading.Event()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">def lighter():</span><br><span class="line">    count &#x3D; 0</span><br><span class="line">    event.set()     #初始值为绿灯</span><br><span class="line">    while True:</span><br><span class="line">        if 5 &lt; count &lt;&#x3D;10 :</span><br><span class="line">            event.clear()  # 红灯，清除标志位</span><br><span class="line">            print(&quot;\33[41;1mred light is on...\033[0m&quot;)</span><br><span class="line">        elif count &gt; 10:</span><br><span class="line">            event.set()  # 绿灯，设置标志位</span><br><span class="line">            count &#x3D; 0</span><br><span class="line">        else:</span><br><span class="line">            print(&quot;\33[42;1mgreen light is on...\033[0m&quot;)</span><br><span class="line"></span><br><span class="line">        time.sleep(1)</span><br><span class="line">        count +&#x3D; 1</span><br><span class="line"></span><br><span class="line">def car(name):</span><br><span class="line">    while True:</span><br><span class="line">        if event.is_set():      #判断是否设置了标志位</span><br><span class="line">            print(&quot;[%s] running...&quot;%name)</span><br><span class="line">            time.sleep(1)</span><br><span class="line">        else:</span><br><span class="line">            print(&quot;[%s] sees red light,waiting...&quot;%name)</span><br><span class="line">            event.wait()</span><br><span class="line">            print(&quot;[%s] green light is on,start going...&quot;%name)</span><br><span class="line"></span><br><span class="line">light &#x3D; threading.Thread(target&#x3D;lighter,)</span><br><span class="line">light.start()</span><br><span class="line"></span><br><span class="line">car &#x3D; threading.Thread(target&#x3D;car,args&#x3D;(&quot;MINI&quot;,))</span><br><span class="line">car.start()</span><br></pre></td></tr></table></figure>

<h4 id="2-9-条件（Condition类）"><a href="#2-9-条件（Condition类）" class="headerlink" title="2.9 条件（Condition类）"></a>2.9 条件（Condition类）</h4><p>使得线程等待，只有满足某条件时，才释放n个线程</p>
<h4 id="2-10-定时器（Timer类）"><a href="#2-10-定时器（Timer类）" class="headerlink" title="2.10 定时器（Timer类）"></a>2.10 定时器（Timer类）</h4><p>定时器，指定n秒后执行某操作</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">from threading import Timer</span><br><span class="line"> </span><br><span class="line"> </span><br><span class="line">def hello():</span><br><span class="line">    print(&quot;hello, world&quot;)</span><br><span class="line"> </span><br><span class="line">t &#x3D; Timer(1, hello)</span><br><span class="line">t.start()  # after 1 seconds, &quot;hello, world&quot; will be printed</span><br></pre></td></tr></table></figure>

<h3 id="3-多进程"><a href="#3-多进程" class="headerlink" title="3 多进程"></a>3 多进程</h3><p>在linux中，每个进程都是由父进程提供的。每启动一个子进程就从父进程克隆一份数据，但是进程之间的数据本身是不能共享的。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">from multiprocessing import Process</span><br><span class="line">import time</span><br><span class="line">def f(name):</span><br><span class="line">    time.sleep(2)</span><br><span class="line">    print(&#39;hello&#39;, name)</span><br><span class="line"> </span><br><span class="line">if __name__ &#x3D;&#x3D; &#39;__main__&#39;:</span><br><span class="line">    p &#x3D; Process(target&#x3D;f, args&#x3D;(&#39;bob&#39;,))</span><br><span class="line">    p.start()</span><br><span class="line">    p.join()</span><br><span class="line">from multiprocessing import Process</span><br><span class="line">import os</span><br><span class="line"> </span><br><span class="line">def info(title):</span><br><span class="line">    print(title)</span><br><span class="line">    print(&#39;module name:&#39;, __name__)</span><br><span class="line">    print(&#39;parent process:&#39;, os.getppid())  #获取父进程id</span><br><span class="line">    print(&#39;process id:&#39;, os.getpid())   #获取自己的进程id</span><br><span class="line">    print(&quot;\n\n&quot;)</span><br><span class="line"> </span><br><span class="line">def f(name):</span><br><span class="line">    info(&#39;\033[31;1mfunction f\033[0m&#39;)</span><br><span class="line">    print(&#39;hello&#39;, name)</span><br><span class="line"> </span><br><span class="line">if __name__ &#x3D;&#x3D; &#39;__main__&#39;:</span><br><span class="line">    info(&#39;\033[32;1mmain process line\033[0m&#39;)</span><br><span class="line">    p &#x3D; Process(target&#x3D;f, args&#x3D;(&#39;bob&#39;,))</span><br><span class="line">    p.start()</span><br><span class="line">    p.join()</span><br></pre></td></tr></table></figure>

<h4 id="3-1-进程间通信"><a href="#3-1-进程间通信" class="headerlink" title="3.1 进程间通信"></a>3.1 进程间通信</h4><p>由于进程之间数据是不共享的，所以不会出现多线程GIL带来的问题。多进程之间的通信通过Queue()或Pipe()来实现</p>
<h5 id="3-1-1-Queue"><a href="#3-1-1-Queue" class="headerlink" title="3.1.1 Queue()"></a>3.1.1 Queue()</h5><p>使用方法跟threading里的queue差不多</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">from multiprocessing import Process, Queue</span><br><span class="line"> </span><br><span class="line">def f(q):</span><br><span class="line">    q.put([42, None, &#39;hello&#39;])</span><br><span class="line"> </span><br><span class="line">if __name__ &#x3D;&#x3D; &#39;__main__&#39;:</span><br><span class="line">    q &#x3D; Queue()</span><br><span class="line">    p &#x3D; Process(target&#x3D;f, args&#x3D;(q,))</span><br><span class="line">    p.start()</span><br><span class="line">    print(q.get())    # prints &quot;[42, None, &#39;hello&#39;]&quot;</span><br><span class="line">    p.join()</span><br></pre></td></tr></table></figure>

<h5 id="3-1-2-Pipe"><a href="#3-1-2-Pipe" class="headerlink" title="3.1.2 Pipe()"></a>3.1.2 Pipe()</h5><p>Pipe的本质是进程之间的数据传递，而不是数据共享，这和socket有点像。pipe()返回两个连接对象分别表示管道的两端，每端都有send()和recv()方法。如果两个进程试图在同一时间的同一端进行读取和写入那么，这可能会损坏管道中的数据。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">from multiprocessing import Process, Pipe</span><br><span class="line"> </span><br><span class="line">def f(conn):</span><br><span class="line">    conn.send([42, None, &#39;hello&#39;])</span><br><span class="line">    conn.close()</span><br><span class="line"> </span><br><span class="line">if __name__ &#x3D;&#x3D; &#39;__main__&#39;:</span><br><span class="line">    parent_conn, child_conn &#x3D; Pipe() </span><br><span class="line">    p &#x3D; Process(target&#x3D;f, args&#x3D;(child_conn,))</span><br><span class="line">    p.start()</span><br><span class="line">    print(parent_conn.recv())   # prints &quot;[42, None, &#39;hello&#39;]&quot;</span><br><span class="line">    p.join()</span><br></pre></td></tr></table></figure>

<h4 id="3-2-Manager"><a href="#3-2-Manager" class="headerlink" title="3.2 Manager"></a>3.2 Manager</h4><p>通过Manager可实现进程间数据的共享。Manager()返回的manager对象会通过一个服务进程，来使其他进程通过代理的方式操作python对象。manager对象支持 <code>list</code>, <code>dict</code>, <code>Namespace</code>, <code>Lock</code>, <code>RLock</code>, <code>Semaphore</code>, <code>BoundedSemaphore</code>, <code>Condition</code>, <code>Event</code>, <code>Barrier</code>, <code>Queue</code>, <code>Value</code> ,<code>Array</code>.</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">from multiprocessing import Process, Manager</span><br><span class="line"> </span><br><span class="line">def f(d, l):</span><br><span class="line">    d[1] &#x3D; &#39;1&#39;</span><br><span class="line">    d[&#39;2&#39;] &#x3D; 2</span><br><span class="line">    d[0.25] &#x3D; None</span><br><span class="line">    l.append(1)</span><br><span class="line">    print(l)</span><br><span class="line"> </span><br><span class="line">if __name__ &#x3D;&#x3D; &#39;__main__&#39;:</span><br><span class="line">    with Manager() as manager:</span><br><span class="line">        d &#x3D; manager.dict()</span><br><span class="line"> </span><br><span class="line">        l &#x3D; manager.list(range(5))</span><br><span class="line">        p_list &#x3D; []</span><br><span class="line">        for i in range(10):</span><br><span class="line">            p &#x3D; Process(target&#x3D;f, args&#x3D;(d, l))</span><br><span class="line">            p.start()</span><br><span class="line">            p_list.append(p)</span><br><span class="line">        for res in p_list:</span><br><span class="line">            res.join()</span><br><span class="line"> </span><br><span class="line">        print(d)</span><br><span class="line">        print(l)</span><br></pre></td></tr></table></figure>

<h4 id="3-3-进程锁（进程同步）"><a href="#3-3-进程锁（进程同步）" class="headerlink" title="3.3 进程锁（进程同步）"></a>3.3 进程锁（进程同步）</h4><p>数据输出的时候保证不同进程的输出内容在同一块屏幕正常显示，防止数据乱序的情况。<br>Without using the lock output from the different processes is liable to get all mixed up.</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">from multiprocessing import Process, Lock</span><br><span class="line"> </span><br><span class="line">def f(l, i):</span><br><span class="line">    l.acquire()</span><br><span class="line">    try:</span><br><span class="line">        print(&#39;hello world&#39;, i)</span><br><span class="line">    finally:</span><br><span class="line">        l.release()</span><br><span class="line"> </span><br><span class="line">if __name__ &#x3D;&#x3D; &#39;__main__&#39;:</span><br><span class="line">    lock &#x3D; Lock()</span><br><span class="line"> </span><br><span class="line">    for num in range(10):</span><br><span class="line">        Process(target&#x3D;f, args&#x3D;(lock, num)).start()</span><br></pre></td></tr></table></figure>

<h4 id="3-4-进程池"><a href="#3-4-进程池" class="headerlink" title="3.4 进程池"></a>3.4 进程池</h4><p>由于进程启动的开销比较大，使用多进程的时候会导致大量内存空间被消耗。为了防止这种情况发生可以使用进程池，（由于启动线程的开销比较小，所以不需要线程池这种概念，多线程只会频繁得切换cpu导致系统变慢，并不会占用过多的内存空间）</p>
<p>进程池中常用方法：<br><code>apply()</code> 同步执行（串行）<br><code>apply_async()</code> 异步执行（并行）<br><code>terminate()</code> 立刻关闭进程池<br><code>join()</code> 主进程等待所有子进程执行完毕。必须在close或terminate()之后。<br><code>close()</code> 等待所有进程结束后，才关闭进程池。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">from  multiprocessing import Process,Pool</span><br><span class="line">import time</span><br><span class="line"> </span><br><span class="line">def Foo(i):</span><br><span class="line">    time.sleep(2)</span><br><span class="line">    return i+100</span><br><span class="line"> </span><br><span class="line">def Bar(arg):</span><br><span class="line">    print(&#39;--&gt;exec done:&#39;,arg)</span><br><span class="line"> </span><br><span class="line">pool &#x3D; Pool(5)  #允许进程池同时放入5个进程</span><br><span class="line"> </span><br><span class="line">for i in range(10):</span><br><span class="line">    pool.apply_async(func&#x3D;Foo, args&#x3D;(i,),callback&#x3D;Bar)  #func子进程执行完后，才会执行callback，否则callback不执行（而且callback是由父进程来执行了）</span><br><span class="line">    #pool.apply(func&#x3D;Foo, args&#x3D;(i,))</span><br><span class="line"> </span><br><span class="line">print(&#39;end&#39;)</span><br><span class="line">pool.close()</span><br><span class="line">pool.join() #主进程等待所有子进程执行完毕。必须在close()或terminate()之后。</span><br></pre></td></tr></table></figure>

<p>进程池内部维护一个进程序列，当使用时，去进程池中获取一个进程，如果进程池序列中没有可供使用的进程，那么程序就会等待，直到进程池中有可用进程为止。在上面的程序中产生了10个进程，但是只能有5同时被放入进程池，剩下的都被暂时挂起，并不占用内存空间，等前面的五个进程执行完后，再执行剩下5个进程。</p>
<h3 id="4-补充：协程"><a href="#4-补充：协程" class="headerlink" title="4 补充：协程"></a>4 补充：协程</h3><p>线程和进程的操作是由程序触发系统接口，最后的执行者是系统，它本质上是操作系统提供的功能。而协程的操作则是程序员指定的，在python中通过yield，人为的实现并发处理。</p>
<p>协程存在的意义：对于多线程应用，CPU通过切片的方式来切换线程间的执行，线程切换时需要耗时。协程，则只使用一个线程，分解一个线程成为多个“微线程”，在一个线程中规定某个代码块的执行顺序。</p>
<p>协程的适用场景：当程序中存在大量不需要CPU的操作时（IO）。<br>常用第三方模块gevent和greenlet。（本质上，gevent是对greenlet的高级封装，因此一般用它就行，这是一个相当高效的模块。）</p>
<h4 id="4-1-greenlet"><a href="#4-1-greenlet" class="headerlink" title="4.1 greenlet"></a>4.1 greenlet</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">from greenlet import greenlet</span><br><span class="line"></span><br><span class="line">def test1():</span><br><span class="line">    print(12)</span><br><span class="line">    gr2.switch()</span><br><span class="line">    print(34)</span><br><span class="line">    gr2.switch()</span><br><span class="line"></span><br><span class="line">def test2():</span><br><span class="line">    print(56)</span><br><span class="line">    gr1.switch()</span><br><span class="line">    print(78)</span><br><span class="line"></span><br><span class="line">gr1 &#x3D; greenlet(test1)</span><br><span class="line">gr2 &#x3D; greenlet(test2)</span><br><span class="line">gr1.switch()</span><br></pre></td></tr></table></figure>

<p>实际上，greenlet就是通过switch方法在不同的任务之间进行切换。</p>
<h4 id="4-2-gevent"><a href="#4-2-gevent" class="headerlink" title="4.2 gevent"></a>4.2 gevent</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">from gevent import monkey; monkey.patch_all()</span><br><span class="line">import gevent</span><br><span class="line">import requests</span><br><span class="line"></span><br><span class="line">def f(url):</span><br><span class="line">    print(&#39;GET: %s&#39; % url)</span><br><span class="line">    resp &#x3D; requests.get(url)</span><br><span class="line">    data &#x3D; resp.text</span><br><span class="line">    print(&#39;%d bytes received from %s.&#39; % (len(data), url))</span><br><span class="line"></span><br><span class="line">gevent.joinall([</span><br><span class="line">        gevent.spawn(f, &#39;https:&#x2F;&#x2F;www.python.org&#x2F;&#39;),</span><br><span class="line">        gevent.spawn(f, &#39;https:&#x2F;&#x2F;www.yahoo.com&#x2F;&#39;),</span><br><span class="line">        gevent.spawn(f, &#39;https:&#x2F;&#x2F;github.com&#x2F;&#39;),</span><br><span class="line">])</span><br></pre></td></tr></table></figure>

<p>通过joinall将任务f和它的参数进行统一调度，实现单线程中的协程。代码封装层次很高，实际使用只需要了解它的几个主要方法即可。</p>
<h2 id="调试代码"><a href="#调试代码" class="headerlink" title="调试代码"></a>调试代码</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">#多进程1</span><br><span class="line">#方法一 直接调用import time</span><br><span class="line">import random</span><br><span class="line">from multiprocessing import Process</span><br><span class="line">def run(name):</span><br><span class="line">    print(&#39;%s runing&#39; %name)</span><br><span class="line">    time.sleep(random.randrange(1,5))</span><br><span class="line">    print(&#39;%s running end&#39; %name)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">p1&#x3D;Process(target&#x3D;run,args&#x3D;(&#39;anne&#39;,)) #必须加,号 </span><br><span class="line">p2&#x3D;Process(target&#x3D;run,args&#x3D;(&#39;alice&#39;,))</span><br><span class="line">p3&#x3D;Process(target&#x3D;run,args&#x3D;(&#39;biantai&#39;,))</span><br><span class="line">p4&#x3D;Process(target&#x3D;run,args&#x3D;(&#39;haha&#39;,))</span><br><span class="line"></span><br><span class="line">p1.start()</span><br><span class="line">p2.start()</span><br><span class="line">p3.start()</span><br><span class="line">p4.start()</span><br><span class="line">print(&#39;主线程&#39;)</span><br></pre></td></tr></table></figure>

<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">#多进程2</span><br><span class="line">#方法二 继承式调用</span><br><span class="line">import time</span><br><span class="line">import random</span><br><span class="line">from multiprocessing import Process</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">class Run(Process):</span><br><span class="line">    def __init__(self,name):</span><br><span class="line">        super().__init__()</span><br><span class="line">        self.name&#x3D;name</span><br><span class="line">    def run(self):</span><br><span class="line">        print(&#39;%s runing&#39; %self.name)</span><br><span class="line">        time.sleep(random.randrange(1,5))</span><br><span class="line">        print(&#39;%s runing end&#39; %self.name)</span><br><span class="line"></span><br><span class="line">p1&#x3D;Run(&#39;anne&#39;)</span><br><span class="line">p2&#x3D;Run(&#39;alex&#39;)</span><br><span class="line">p3&#x3D;Run(&#39;ab&#39;)</span><br><span class="line">p4&#x3D;Run(&#39;hey&#39;)</span><br><span class="line">p1.start() #start会自动调用run</span><br><span class="line">p2.start()</span><br><span class="line">p3.start()</span><br><span class="line">p4.start()</span><br><span class="line">print(&#39;主线程&#39;)</span><br></pre></td></tr></table></figure>

 
      <!-- reward -->
      
      <div id="reword-out">
        <div id="reward-btn">
          打赏
        </div>
      </div>
      
    </div>
    

    <!-- copyright -->
    
    <div class="declare">
      <ul class="post-copyright">
        <li>
          <i class="ri-copyright-line"></i>
          <strong>版权声明： </strong>
          
          本博客所有文章除特别声明外，著作权归作者所有。转载请注明出处！
          
        </li>
      </ul>
    </div>
    
    <footer class="article-footer">
       
<div class="share-btn">
      <span class="share-sns share-outer">
        <i class="ri-share-forward-line"></i>
        分享
      </span>
      <div class="share-wrap">
        <i class="arrow"></i>
        <div class="share-icons">
          
          <a class="weibo share-sns" href="javascript:;" data-type="weibo">
            <i class="ri-weibo-fill"></i>
          </a>
          <a class="weixin share-sns wxFab" href="javascript:;" data-type="weixin">
            <i class="ri-wechat-fill"></i>
          </a>
          <a class="qq share-sns" href="javascript:;" data-type="qq">
            <i class="ri-qq-fill"></i>
          </a>
          <a class="douban share-sns" href="javascript:;" data-type="douban">
            <i class="ri-douban-line"></i>
          </a>
          <!-- <a class="qzone share-sns" href="javascript:;" data-type="qzone">
            <i class="icon icon-qzone"></i>
          </a> -->
          
          <a class="facebook share-sns" href="javascript:;" data-type="facebook">
            <i class="ri-facebook-circle-fill"></i>
          </a>
          <a class="twitter share-sns" href="javascript:;" data-type="twitter">
            <i class="ri-twitter-fill"></i>
          </a>
          <a class="google share-sns" href="javascript:;" data-type="google">
            <i class="ri-google-fill"></i>
          </a>
        </div>
      </div>
</div>

<div class="wx-share-modal">
    <a class="modal-close" href="javascript:;"><i class="ri-close-circle-line"></i></a>
    <p>扫一扫，分享到微信</p>
    <div class="wx-qrcode">
      <img src="//api.qrserver.com/v1/create-qr-code/?size=150x150&data=http://example.com/2020/11/11/develop/%E6%90%9E%E5%AE%9Apython%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%92%8C%E5%A4%9A%E8%BF%9B%E7%A8%8B/" alt="微信分享二维码">
    </div>
</div>

<div id="share-mask"></div>  
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/develop/" rel="tag">develop</a></li></ul>

    </footer>
  </div>

   
  <nav class="article-nav">
    
      <a href="/2020/11/11/develop/%E6%8F%90%E9%AB%98%E7%94%9F%E4%BA%A7%E6%95%88%E7%8E%87%EF%BC%813%E4%B8%AA%E5%B8%B8%E7%94%A8%E7%9A%84%E5%BC%80%E6%BA%90%E5%B7%A5%E5%85%B7%E5%BA%93%E5%88%86%E4%BA%AB/" class="article-nav-link">
        <strong class="article-nav-caption">上一篇</strong>
        <div class="article-nav-title">
          
            提高生产效率！3个常用的开源工具库分享.md
          
        </div>
      </a>
    
    
      <a href="/2020/11/11/django/1-Django%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B/" class="article-nav-link">
        <strong class="article-nav-caption">下一篇</strong>
        <div class="article-nav-title">1-Django快速上手.md</div>
      </a>
    
  </nav>

   
<!-- valine评论 -->
<div id="vcomments-box">
  <div id="vcomments"></div>
</div>
<script src="//cdn1.lncld.net/static/js/3.0.4/av-min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/valine@1.4.14/dist/Valine.min.js"></script>
<script>
  new Valine({
    el: "#vcomments",
    app_id: "",
    app_key: "",
    path: window.location.pathname,
    avatar: "monsterid",
    placeholder: "给我的文章加点评论吧~",
    recordIP: true,
  });
  const infoEle = document.querySelector("#vcomments .info");
  if (infoEle && infoEle.childNodes && infoEle.childNodes.length > 0) {
    infoEle.childNodes.forEach(function (item) {
      item.parentNode.removeChild(item);
    });
  }
</script>
<style>
  #vcomments-box {
    padding: 5px 30px;
  }

  @media screen and (max-width: 800px) {
    #vcomments-box {
      padding: 5px 0px;
    }
  }

  #vcomments-box #vcomments {
    background-color: #fff;
  }

  .v .vlist .vcard .vh {
    padding-right: 20px;
  }

  .v .vlist .vcard {
    padding-left: 10px;
  }
</style>

 
     
</article>

</section>
      <footer class="footer">
  <div class="outer">
    <ul>
      <li>
        Copyrights &copy;
        2015-2020
        <i class="ri-heart-fill heart_icon"></i> TzWind
      </li>
    </ul>
    <ul>
      <li>
        
        
        
        由 <a href="https://hexo.io" target="_blank">Hexo</a> 强力驱动
        <span class="division">|</span>
        主题 - <a href="https://github.com/Shen-Yu/hexo-theme-ayer" target="_blank">Ayer</a>
        
      </li>
    </ul>
    <ul>
      <li>
        
        
        <span>
  <span><i class="ri-user-3-fill"></i>访问人数:<span id="busuanzi_value_site_uv"></span></s>
  <span class="division">|</span>
  <span><i class="ri-eye-fill"></i>浏览次数:<span id="busuanzi_value_page_pv"></span></span>
</span>
        
      </li>
    </ul>
    <ul>
      
    </ul>
    <ul>
      
    </ul>
    <ul>
      <li>
        <!-- cnzz统计 -->
        
        <script type="text/javascript" src='https://s9.cnzz.com/z_stat.php?id=1278069914&amp;web_id=1278069914'></script>
        
      </li>
    </ul>
  </div>
</footer>
      <div class="float_btns">
        <div class="totop" id="totop">
  <i class="ri-arrow-up-line"></i>
</div>

<div class="todark" id="todark">
  <i class="ri-moon-line"></i>
</div>

      </div>
    </main>
    <aside class="sidebar on">
      <button class="navbar-toggle"></button>
<nav class="navbar">
  
  <div class="logo">
    <a href="/"><img src="/images/ayer-side.svg" alt="Hexo"></a>
  </div>
  
  <ul class="nav nav-main">
    
    <li class="nav-item">
      <a class="nav-item-link" href="/">主页</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/archives">归档</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/categories">分类</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags">标签</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" target="_blank" rel="noopener" href="http://www.baidu.com">百度</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/friends">友链</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/2019/about">关于我</a>
    </li>
    
  </ul>
</nav>
<nav class="navbar navbar-bottom">
  <ul class="nav">
    <li class="nav-item">
      
      <a class="nav-item-link nav-item-search"  title="搜索">
        <i class="ri-search-line"></i>
      </a>
      
      
      <a class="nav-item-link" target="_blank" href="/atom.xml" title="RSS Feed">
        <i class="ri-rss-line"></i>
      </a>
      
    </li>
  </ul>
</nav>
<div class="search-form-wrap">
  <div class="local-search local-search-plugin">
  <input type="search" id="local-search-input" class="local-search-input" placeholder="Search...">
  <div id="local-search-result" class="local-search-result"></div>
</div>
</div>
    </aside>
    <script>
      if (window.matchMedia("(max-width: 768px)").matches) {
        document.querySelector('.content').classList.remove('on');
        document.querySelector('.sidebar').classList.remove('on');
      }
    </script>
    <div id="mask"></div>

<!-- #reward -->
<div id="reward">
  <span class="close"><i class="ri-close-line"></i></span>
  <p class="reward-p"><i class="ri-cup-line"></i>请我喝杯咖啡吧~</p>
  <div class="reward-box">
    
    
  </div>
</div>
    
<script src="/js/jquery-2.0.3.min.js"></script>


<script src="/js/lazyload.min.js"></script>

<!-- Tocbot -->


<script src="/js/tocbot.min.js"></script>

<script>
  tocbot.init({
    tocSelector: '.tocbot',
    contentSelector: '.article-entry',
    headingSelector: 'h1, h2, h3, h4, h5, h6',
    hasInnerContainers: true,
    scrollSmooth: true,
    scrollContainer: 'main',
    positionFixedSelector: '.tocbot',
    positionFixedClass: 'is-position-fixed',
    fixedSidebarOffset: 'auto'
  });
</script>

<script src="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.css">
<script src="https://cdn.jsdelivr.net/npm/justifiedGallery@3.7.0/dist/js/jquery.justifiedGallery.min.js"></script>

<script src="/dist/main.js"></script>

<!-- ImageViewer -->

<!-- Root element of PhotoSwipe. Must have class pswp. -->
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">

    <!-- Background of PhotoSwipe. 
         It's a separate element as animating opacity is faster than rgba(). -->
    <div class="pswp__bg"></div>

    <!-- Slides wrapper with overflow:hidden. -->
    <div class="pswp__scroll-wrap">

        <!-- Container that holds slides. 
            PhotoSwipe keeps only 3 of them in the DOM to save memory.
            Don't modify these 3 pswp__item elements, data is added later on. -->
        <div class="pswp__container">
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
        </div>

        <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
        <div class="pswp__ui pswp__ui--hidden">

            <div class="pswp__top-bar">

                <!--  Controls are self-explanatory. Order can be changed. -->

                <div class="pswp__counter"></div>

                <button class="pswp__button pswp__button--close" title="Close (Esc)"></button>

                <button class="pswp__button pswp__button--share" style="display:none" title="Share"></button>

                <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>

                <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>

                <!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR -->
                <!-- element will get class pswp__preloader--active when preloader is running -->
                <div class="pswp__preloader">
                    <div class="pswp__preloader__icn">
                        <div class="pswp__preloader__cut">
                            <div class="pswp__preloader__donut"></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
                <div class="pswp__share-tooltip"></div>
            </div>

            <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
            </button>

            <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
            </button>

            <div class="pswp__caption">
                <div class="pswp__caption__center"></div>
            </div>

        </div>

    </div>

</div>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/default-skin/default-skin.min.css">
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe-ui-default.min.js"></script>

<script>
    function viewer_init() {
        let pswpElement = document.querySelectorAll('.pswp')[0];
        let $imgArr = document.querySelectorAll(('.article-entry img:not(.reward-img)'))

        $imgArr.forEach(($em, i) => {
            $em.onclick = () => {
                // slider展开状态
                // todo: 这样不好，后面改成状态
                if (document.querySelector('.left-col.show')) return
                let items = []
                $imgArr.forEach(($em2, i2) => {
                    let img = $em2.getAttribute('data-idx', i2)
                    let src = $em2.getAttribute('data-target') || $em2.getAttribute('src')
                    let title = $em2.getAttribute('alt')
                    // 获得原图尺寸
                    const image = new Image()
                    image.src = src
                    items.push({
                        src: src,
                        w: image.width || $em2.width,
                        h: image.height || $em2.height,
                        title: title
                    })
                })
                var gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, {
                    index: parseInt(i)
                });
                gallery.init()
            }
        })
    }
    viewer_init()
</script>

<!-- MathJax -->

<!-- Katex -->

<!-- busuanzi  -->


<script src="/js/busuanzi-2.3.pure.min.js"></script>


<!-- ClickLove -->

<!-- ClickBoom1 -->

<!-- ClickBoom2 -->

<!-- CodeCopy -->


<link rel="stylesheet" href="/css/clipboard.css">

<script src="https://cdn.jsdelivr.net/npm/clipboard@2/dist/clipboard.min.js"></script>
<script>
  function wait(callback, seconds) {
    var timelag = null;
    timelag = window.setTimeout(callback, seconds);
  }
  !function (e, t, a) {
    var initCopyCode = function(){
      var copyHtml = '';
      copyHtml += '<button class="btn-copy" data-clipboard-snippet="">';
      copyHtml += '<i class="ri-file-copy-2-line"></i><span>COPY</span>';
      copyHtml += '</button>';
      $(".highlight .code pre").before(copyHtml);
      $(".article pre code").before(copyHtml);
      var clipboard = new ClipboardJS('.btn-copy', {
        target: function(trigger) {
          return trigger.nextElementSibling;
        }
      });
      clipboard.on('success', function(e) {
        let $btn = $(e.trigger);
        $btn.addClass('copied');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-checkbox-circle-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPIED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-checkbox-circle-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
      clipboard.on('error', function(e) {
        e.clearSelection();
        let $btn = $(e.trigger);
        $btn.addClass('copy-failed');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-time-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPY FAILED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-time-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
    }
    initCopyCode();
  }(window, document);
</script>


<!-- CanvasBackground -->


    
  </div>
</body>

</html>